通常通过method swizzle可以交换两个方法的实现(不限于同一个类型),先看一段代码:

People类

1
2
3
4
- (void)talk
{
NSLog(@"%@", self.class);
}

Student类继承People

Student

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+ (void)load
{
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

SEL originalSelector = NSSelectorFromString(@"talk");

SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk");

Method originalMethod = class_getInstanceMethod(self.class, originalSelector);
Method swizzleMethod = class_getInstanceMethod(self.class,
swizzleSelector);
method_exchangeImplementations(originalMethod, swizzleMethod);
});
}

- (void)swizzle_talk
{
NSLog(@"swizzle_talk: %@", self.class);
}

Method在objc-private.h中有如下定义:

1
typedef struct old_method *Method;

old_method是结构体,它定义在objc-runtime-old.h中:

1
2
3
4
5
struct old_method {
SEL method_name;
char *method_types;
IMP method_imp;
}

Method中包含了3个部分,第一部分是函数名,通常可以通过@selector()获取,第二部分是函数声明, 第三部分是函数实现,理解成函数指针。

class_getInstanceMethod有两个参数,第一个参数是class,第二个参数是selector。这个函数是以class开头的,第一个参数也是传的class对象,所以可以理解为从所传递的类对象中查找指定的数据,类对象可以通过实例对象的class方法活的,类对象全局只有一个。

Class对象的定义如下:

1
typedef struct objc_class *Class;

也就是说Class对象其实是objc_class结构体,平时使用的self.class得到的是一个objc_class的结构体指针。

objc_class定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct objc_class : objc_object {

Class superclass;

const char *name;

uint32_t version;

uint32_t info;

uint32_t instance_size;

struct old_ivar_list *ivars;

struct old_method_list **methodLists;

Cache cache;

struct old_protocol_list *protocols;

// CLS_EXT only

const uint8_t *ivar_layout;

struct old_class_ext *ext;
}

这里只列出了字段,函数并没有列出。可以看到一个类对象里面包含了以下比较重要的信息:

1.它的基类对象字段superclass

2.它的实例对象有哪些字段 ivars

3.它的实例对象有哪些方法,存储在方法列表中 **methodLists, 这里为什么是指针的指针,就是它可能包含多个方法列表。

4.它属于什么类型的类对象:info,比如CLS_CLASS还是CLS_META,相当于类对象自己的元数据信息。通过它可以判断出一个类对象是否是元类对象。

以下是class_getInstanceMethod的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Method class_getInstanceMethod(Class cls, SEL sel)

{

if (!cls || !sel) return nil;




// This deliberately avoids +initialize because it historically did so.




// This implementation is a bit weird because it's the only place that

// wants a Method instead of an IMP.




Method meth;

meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);

if (meth == (Method)1) {

// Cache contains forward:: . Stop searching.

return nil;

} else if (meth) {

return meth;

}



// Search method lists, try method resolver, etc.

lookUpImpOrNil(cls, sel, nil,

NO/*initialize*/, NO/*cache*/, YES/*resolver*/);




meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);

if (meth == (Method)1) {

// Cache contains forward:: . Stop searching.

return nil;

} else if (meth) {

return meth;

}




return _class_getMethod(cls, sel);

}

这一部分主要是先从方法缓存里取方法,主要看下_class_getMethod

1
2
3
4
5
6
static Method _class_getMethod(Class cls, SEL sel)
{
mutex_locker_t lock(methodListLock);

return (Method)_getMethod(cls, sel);
}

在_class_getMethod中调用了_getMethod函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static inline old_method * _getMethod(Class cls, SEL sel) {

for (; cls; cls = cls->superclass) {

old_method *m;

m = _findMethodInClass(cls, sel);

if (m) return m;

}

return nil;

}

_getMethod是主要的实现了,这里通过_findMethodInClass函数来查找类对象的方法,并且便利了父类对象。也就是说,基类中的方法也会被遍历到。

继续再看下_findMethodInClass函数的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
static inline old_method * _findMethodInClass(Class cls, SEL sel) {

// Flattened version of nextMethodList(). The optimizer doesn't

// do a good job with hoisting the conditionals out of the loop.

// Conceptually, this looks like:

// while ((mlist = nextMethodList(cls, &iterator))) {

// old_method *m = _findMethodInList(mlist, sel);

// if (m) return m;

// }




if (!cls->methodLists) {

// No method lists.

return nil;

}

else if (cls->info & CLS_NO_METHOD_ARRAY) {

// One method list.

old_method_list **mlistp;

mlistp = (old_method_list **)&cls->methodLists;

*mlistp = fixupSelectorsInMethodList(cls, *mlistp);

return _findMethodInList(*mlistp, sel);

}

else {

// Multiple method lists.

old_method_list **mlistp;

for (mlistp = cls->methodLists;

*mlistp != nil && *mlistp != END_OF_METHODS_LIST;

mlistp++)

{

old_method *m;

*mlistp = fixupSelectorsInMethodList(cls, *mlistp);

m = _findMethodInList(*mlistp, sel);

if (m) return m;

}

return nil;

}

}

static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) {

int i;

if (!mlist) return nil;

for (i = 0; i < mlist->method_count; i++) {

old_method *m = &mlist->method_list[i];

if (m->method_name == sel) {

return m;

}

}

return nil;

}

这个方法主要是通过遍历类对象的方法列表字段,来查找某个方法。

在_findMethodInList函数中,它其实是比较了方法列表中方法的Selector和要找的Selector是不是同一个来查找这个方法。所以通过selector就可以定位到一个method,也就是可以得到它的IMP和Type了。

所以可以很好理解一下2个方法:

method_getTypeEncoding

method_getImplementation

通过以上分析,可以知道class_getInstanceMethod是获得某个类对象中的方法对象,这个过程中会遍历到父类中。也就是当前类没有实现的方法,父类来抵,也符合面向对象的设计。

总的说来,class_getxxxxxxx是通过查找类对象内部数据来得到一些消息,类似的还有

class_getClassMethod,它是获取类方法的函数:

看看它的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}

Class getMeta() {

if (isMetaClass()) return (Class)this;

else return this->ISA();
}

bool isMetaClass() {
return info & CLS_META;
}

#define CLS_CLASS 0X1
#define CLS_META 0x2

可以知道如果当前类就是元类对象,就返回它自己反之返回this→ISA();

objc_class继承自objc_object,函数ISA是objc_object中定义的:

1
2
3
4
5
6
7
8
9
10
11
truct objc_object {

private:

isa_t isa;
}

uion isa_t {

Class clas;
}

相当于取出objc_class对象的cls信息,也就是元类对象了。

然后通过cls_getInstanceMethod来去到Method信息,跟之前取类对象中的Method一样,只是多了一步取元类对象的步骤。

在理解了class_getInstanceMethod函数之后,再来看一下class_addMethod函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

{

IMP old;

if (!cls) return NO;

old = _class_addMethod(cls, name, imp, types, NO);

return !old;

}
static IMP _class_addMethod(Class cls, SEL name, IMP imp,

const char *types, bool replace)
{

old_method *m;

IMP result = nil;




if (!types) types = "";




mutex_locker_t lock(methodListLock);




if ((m = _findMethodInClass(cls, name))) {

// already exists

// fixme atomic

result = method_getImplementation((Method)m);

if (replace) {

method_setImplementation((Method)m, imp);

}

} else {

// fixme could be faster

old_method_list *mlist =

(old_method_list *)calloc(sizeof(old_method_list), 1);

mlist->obsolete = fixed_up_method_list;

mlist->method_count = 1;

mlist->method_list[0].method_name = name;

mlist->method_list[0].method_types = strdup(types);

mlist->method_list[0].method_imp = imp;



_objc_insertMethods(cls, mlist, nil);

if (!(cls->info & CLS_CONSTRUCTING)) {

flush_caches(cls, NO);

} else {

// in-construction class has no subclasses

flush_cache(cls);

}

result = nil;

}

return result;

}

相当于当前类对象中存在这个方法的时候(包括父类的),什么都不会处理返回NO。如果不存在那么会添加一个,并且返回YES。

接着是class_replaceMethod

1
2
3
4
5
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
return _class_addMethod(cls, name, imp, types, YES);
}

该方法和class_addMethod的区别是,如果发现已经存在sel对应的Method,前者会直接通过新的imp覆盖原来的method,后者则不会做任何处理。

最后method_exchangeImplementations交换两个method的实现。

现在分析一下文章开头那段代码,当当前类本身没有实现original_selector方法的时候,但是它的基类实现了。那么最后交换的就是基类中的original_selector方法,这将会影响基类和其他继承子类的行为。现在通过一个简单的demo来验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@interface People : NSObject

- (void)talk;

@end
@implementation People

- (void)talk

{

NSLog(@"%@", self.class);

}




@interface Student : People

@end

@implemention Student

@end



@interface Teacher : People

@end


@implemention Teacher

@end
@interface Student (Tracking)

@end


@implemention Student

+ (void)load

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

SEL originalSelector = NSSelectorFromString(@"talk");

SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk");

Method originalMethod = class_getInstanceMethod(self.class,
originalSelector);

Method swizzleMethod = class_getInstanceMethod(self.class,
swizzleSelector);

method_exchangeImplementations(originalMethod, swizzleMethod);
});
}



- (void)swizzle_talk

{

NSLog(@"zwizzle_talk: %@", self.class);

}

@end

- (void)viewDidLoad {

[super viewDidLoad];


Teacher *t = [[Teacher alloc] init];

[t talk];

Student *stu = [[Student alloc] init];

[stu talk];

}

@end

输出是:

1
2
3
20:15:35.432 abc[87901:2148310] zwizzle_talk: Teacher

20:15:35.433 abc[87901:2148310] zwizzle_talk: Student

说明 Teacher类也收到了student swizzle的影响。

Student(Tracking)换一种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
+ (void)load
{
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

SEL originalSelector = NSSelectorFromString(@"talk");

SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk");

Method originalMethod = class_getInstanceMethod(self.class,
originalSelector);

Method swizzleMethod = class_getInstanceMethod(self.class,
swizzleSelector);

BOOL addMethod = class_addMethod(self.class, originalSelector,
method_getImplementation(swizzleMethod), method_getTypeEncoding(
swizzleMethod));

if (addMethod) {
class_replaceMethod(self.class, swizzleSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzleMethod);
}
});
}

输出是:

1
2
3
20:19:50.683 abc[87966:2152486] Teacher

20:19:50.684 abc[87966:2152486] zwizzle_talk: Student

可以看到,Teacher类并没有收到影响,虽然是基类中实现了talk方法,但是通过class_addMethod给当前类Student动态增加了talk的实现,然后进行交换。没有影响到原来People类中的talk方法。

可以看出,第二种方法实现起来更好,影响范围更小一些。